package ar.uba.fi.tonyvaliente.signature.query;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;

import ar.uba.fi.tonyvaliente.documents.Document;
import ar.uba.fi.tonyvaliente.documents.DocumentManager;
import ar.uba.fi.tonyvaliente.signature.HashFunction;
import ar.uba.fi.tonyvaliente.signature.HashFunctionException;
import ar.uba.fi.tonyvaliente.tree.BTree;
import ar.uba.fi.tonyvaliente.utils.Pair;

/**
 * Realiza las consultas sobre los signature files.
 * @author santiago
 *
 */
public class QueryDocuments {

	private List<HashFunction> hashFunctions;
	private int hashLength;
	private BTree<Integer, BitSet> signatures;
	
	public QueryDocuments(List<HashFunction> hashFunctions, int hashLength, 
			BTree<Integer, BitSet> signatures){
		this.hashFunctions = hashFunctions;
		this.hashLength = hashLength;
		this.signatures = signatures;
	}
	
	/**
	 * Consulta los signatureFiles, devuelve todos los documentos retornados por
	 * la consulta que contengan la palabras indicada.
	 * 
	 * pre: la palabra debe haber pasado por los mismos filtros que al almacenarlas.
	 * @param term
	 * @return
	 * @throws SignatureQueryException 
	 */
	public List<Integer> query(String term) throws SignatureQueryException{
		

		// Primero calculamos la firma de la consulta
		BitSet querySignature = new BitSet(hashLength);
		querySignature.or(this.calculateWordSignature(term));
		
		List<Integer> possibleDocuments = queryDocuments(querySignature);
		System.out.println("possible documents: " + possibleDocuments.size());
		List<Integer> effectiveDocuments = checkDocuments(possibleDocuments, term);
		
		return effectiveDocuments;
	}

	private List<Integer> checkDocuments(List<Integer> possibleDocuments, String term) {
		List<Integer> effectiveDocuments = new ArrayList<Integer>();
		// Por último corroboramos todos los documentos obtenidos.
		for (Integer possibleDocID : possibleDocuments) {
			
			Document document = DocumentManager.getDocument(possibleDocID);
			//Obtener el documento por su ID
			if(document.getContent().contains(term)){
				effectiveDocuments.add(possibleDocID);
			}

			
		}
		return effectiveDocuments;
	}

	private List<Integer> queryDocuments(BitSet querySignature) {
		Iterator<Pair<Integer, BitSet>> signatureIterator = signatures.iterator();
		
		List<Integer> possibleDocuments = new ArrayList<Integer>();
		
		// Luego hacemos un and entre cada documento y la firma.
		// Si el resultado del and es igual a la firma el documento es un candidato.
		while (signatureIterator.hasNext()) {
			Pair<Integer, BitSet> pair = (Pair<Integer, BitSet>) signatureIterator.next();
			BitSet documentSignature = pair.getSecond();
			documentSignature.and(querySignature);
			if(documentSignature.equals(querySignature)){
				possibleDocuments.add(pair.getFirst());
			}
		}
		return possibleDocuments;
	}
	
	private BitSet calculateWordSignature(String word) throws SignatureQueryException{
	
		BitSet wordSignature = new BitSet(hashLength);
		for (HashFunction hashFunction : hashFunctions) {
			// TODO si siempre vamos a castear a int mejor dejar que el método devuelva int y listo
			// sobre todo si el bitSet no soporta muchos bits.
			// Si se necesitan más bits hay que buscar una alternativa al bitSet
			Integer hashValue;
			try {
				hashValue = hashFunction.hash(word).intValue();
			} catch (HashFunctionException e) {
				throw new SignatureQueryException("Se produjo un error procesando la firma de la palabra", e);
			}
			wordSignature.set(hashValue, true);
		}
		return wordSignature;
	}
	
}
